#include "../hdr/b64_decode.h"

/*	Registra los errores de decodificación en stream
	recibido por parámetro. */
void decode_log_error(FILE* error, int line, int byte, const char* msg)
{
	time_t rawtime;
	struct tm* timeinfo;

	time(&rawtime);
	timeinfo = localtime(&rawtime);

	fprintf(error, "%s\tError: línea %d, caracter %d: %s.\n", asctime(timeinfo),
			line, byte, msg);
}

/*	Dado un caracter codificado en base 64, devuelve su 
	equivalente en ASCII. En caso de no ser un caracter
	valido se devolvera -1. */
char base64_to_ascii(char c)
{
	if (c >= 'A' && c <= 'Z')
		return c - 'A';
	else if (c >= 'a' && c <= 'z')
		return c - 'a' + 26;
	else if (c >= '0' && c <= '9')
		return c - '0' + 52;
	else
		switch (c) 
		{
			case '+':
				return 62;
			case '/':
				return 63;
			case '=':
				return 0;
		}
	return -1;
}

int is_little_endian();
/*	Funcion que inverte el orden de los cuatro bytes del
	bloque recibido por parámetro. */

void invert_block_on_little_endian(char* block)
{
	char aux = block[3];
	if(!is_little_endian())
		return;
	block[3] = block[0];
	block[0] = aux;
	aux = block[2];
	block[2] = block[1];
	block[1] = aux;
}

int is_little_endian()
{
	int n = 0xff;
	char* p = (char*)&n;
	if(p[0] != 0)
		return 1;
	return 0;
}

/*	Dado un bloque de cuatro caracteres codificados en base 64, 
	decodifica el bloque, guardandolo en decoded_blocky devuelve
	la cantidad de bytes validos leidos. */
int decode_block(char* block, char* decoded_block) 
{
	/*	Reporta padding invalidos, devolviendo su posición 
		relativa al final del bloque. */
	if (block[0] == '=')
		return -3;
	if (block[1] == '=')
		return -2;
	if ((block[2] == '=') & (block[3] != '=')) 
		return -1;

	/*  Se define la mascara. */
	unsigned int filter = 0xfc000000;
	unsigned int *_block, *_decoded_block;
	char aux_block[4];
	int i;

	memset(decoded_block, 0, 4);
	
	/* 	Se tratan los bytes como enteros. */
	_block = (unsigned int*) aux_block;
	_decoded_block = (unsigned int*) decoded_block;

	for(i = 0; i < 4; i++)
		aux_block[i] = base64_to_ascii(block[i]);
	
	invert_block_on_little_endian(aux_block);

	/*	En este bucle se aplica la mascara cuatro veces para obtener
		la informacion decodificada. */
	for (i = 1; i <= 4; i++) {
		*_decoded_block = *_decoded_block | (filter & (*_block << (2 * i)));
		filter = filter >> 6;
	}

	invert_block_on_little_endian(decoded_block);
	
	/*	Se reporta la cantidad de bytes leidos. */
	if(block[2] == '=')
		return 1;
	if(block[3] == '=')
		return 2;
	return 3;
}

/*  Funcion principal para la decodificacion de archivos en 
	base 64. Se leera secuencialmente el contenido del archivo
	de entrada, agrupandose la informacion en bloques de
	cuatro bytes, se decodificaran los datos llamando a 
	decode_block y si estos son validos se guardaran en el 
	archivo de salida.  */
int decode_file(FILE* input, FILE* output, FILE* error) 
{
	/*  Declaracion de variables:
		- block es la variable en donde se almacenaran los cuatro
			bytes leios de la entrada.
		- i es un contador usado para recorrer los bytes de block.
		- n representa la cantidad de caracteres validos reconocidos
			despues de la decodificacion.
		- line y byte haran referencia al contenido del archivo de
			entrada en caso de que se tenga que reportar algun error. */
	int i = 0, n = 3, line = 1, byte = 1;
	char block[4], decoded_block[4];

	/*	Lee hasta llegar al final del archivo. */
	while(fread(&(block[i]), sizeof(char), 1, input)) 
	{
		/*	Registra el caso en el que se lea un salto de linea. */
		if(block[i] == '\n')
		{
			line++;
			byte = 1;
		}
		else
		{
			/*	Si el caracter es invalido se reporta el error. */
			char aux = base64_to_ascii(block[i]);
			if(aux == -1)
			{
				decode_log_error(error, line, byte, "Caracter inválido");
				return RES_ERROR;
			}
			/*  Si hubo un padding anteriormente. */
			if(n != 3)
			{
				decode_log_error(error, line, byte + n - 3,
						"Padding antes de fin de archivo");
				return RES_ERROR;
			}
			/*	Sino, se guarda la decodificacion. */
			i++;
			byte++;
		}
	
		/*	Al completar cuatro bytes validos se decodifican en conjunto. */
		if(i == 4)
		{
			n = decode_block(block, decoded_block);
			if(n < 0)
			{
				decode_log_error(error, line, byte + n - 1,
					   "Padding inválido");
				return RES_ERROR;
			}
			fwrite(decoded_block, sizeof(char), n, output);
			i = 0;
		}
	}

	/*	En caso de que el archivo no tenga un numero divisible por cuatro de
		bytes se reportara como invalido. */
	if(i)
	{
		decode_log_error(error, line, byte, "Fin de archivo inesperado");
		return RES_ERROR;
	}

	return RES_OK;
}
